/**
 * @file lv_flashfs.c
 * Implementation of flash file system which do NOT support directories.
 * The API is compatible with the lv_fs_int module.
 */
/*********************
 *      INCLUDES
 *********************/
#include "lv_flashfs.h"

#if LV_USE_FILESYSTEM && USE_LV_FLASH_FS
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "lv_fs.h"
#include "../../board.h"

/*********************
 *      DEFINES
 *********************/
#define ERR_FILE_NO_SPACE (0x31)
#define LFS_ERR_NOSPC     (-28)

/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
/**********************
 *  STATIC VARIABLES
 **********************/
static int  flashfs_inited = 0;

/**************************************************************************************************************
 *      MACROS
 **************************************************************************************************************/

extern ERR_CODE FDI_ferror(FILE_ID stream);
extern unsigned int FDI_GetFreeSpaceSize(void);
extern unsigned int FDI_GetUsedSpaceSize(void);
extern unsigned int fs_mode_is_tfs(void);

/**************************************************************************************************************
 *   STATIC FUNCTIONS
 **************************************************************************************************************/
/**
 * Give the state of the flashfs
 * @return true if flashfs is initialized and can be used else false
 */
static bool lv_flashfs_ready(lv_fs_drv_t *drv)
{
    return flashfs_inited == 1;
}

/**
 * Open a file in flashfs
 * @param file_p pointer to a lv_flashfs_file_t variable
 * @param fname name of the file. There are no directories so e.g. "myfile.txt"
 * @param mode element of 'fs_mode_t' enum or its 'OR' connection (e.g.
 * FS_MODE_WR | FS_MODE_RD)
 * @return LV_FS_RES_OK: no error, the file is opened
 *         any error from lv__fs_res_t enum
 */
static lv_fs_res_t lv_flashfs_open(lv_fs_drv_t *drv, void *file_p, const char *fname, lv_fs_mode_t mode)
{
    const char *flags = "";
    uint32_t *fd = (uint32_t *)file_p;

    if (mode == LV_FS_MODE_WR) flags = "wb";
    else if (mode == LV_FS_MODE_RD) flags = "rb";
    else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) flags = "wb+";
    else if (mode == (LV_FS_MODE_RD | LV_FS_MODE_APPEND)) flags = "rb+";
    else if (mode == (LV_FS_MODE_WR | LV_FS_MODE_APPEND)) flags = "ab+";
    else flags = "wb+";

    *fd = FDI_fopen(fname, flags);
    if (*fd == 0) {
        return LV_FS_RES_UNKNOWN;
    }

    return LV_FS_RES_OK;
}

/**
 * Close an opened file
 * @param file_p pointer to an 'flashfs_file_t' variable. (opened with lv_flashfs_open)
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv__fs_res_t enum
 */
static lv_fs_res_t lv_flashfs_close(lv_fs_drv_t *drv, void *file_p)
{
    uint32_t fd = *(uint32_t *)file_p;
    int32_t ret = 0;

    if (fd == 0) {
        return LV_FS_RES_OK;
    }

    ret = FDI_fclose(fd);
    if (ret < 0) {
        return LV_FS_RES_INV_PARAM;
    }

    return LV_FS_RES_OK;
}

/**
 * Remove a file. The file can not be opened.
 * @param fname '\0' terminated string
 * @return LV_FS_RES_OK: no error, the file is removed
 *         LV_FS_RES_DENIED: the file was opened, remove failed
 */
static lv_fs_res_t lv_flashfs_remove(lv_fs_drv_t *drv, const char *fname)
{
    int32_t ret = 0;

    ret= FDI_remove(fname);
    if (ret < 0) {
        return LV_FS_RES_UNKNOWN;
    }

    return LV_FS_RES_OK;
}

/**
 * Read data from an opened file
 * @param file_p pointer to an 'flashfs_file_t' variable. (opened with lv_flashfs_open )
 * @param buf pointer to a memory block where to store the read data
 * @param btr number of Bytes To Read
 * @param br the real number of read bytes (Byte Read)
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv__fs_res_t enum
 */
static lv_fs_res_t lv_flashfs_read(lv_fs_drv_t *drv, void *file_p, void *buf, uint32_t btr, uint32_t *br)
{
    uint32_t fd = *(uint32_t *)file_p;
    int32_t ret = 0;

    *br = 0;
    if (fd == 0) {
        return LV_FS_RES_INV_PARAM;
    }

    ret= FDI_fread(buf, 1, btr,  fd);
    *br = ret;
    if (ret != btr) {
         return LV_FS_RES_UNKNOWN;
    }

    return LV_FS_RES_OK;
}

/**
 * Write data to an opened file
 * @param file_p pointer to an 'flashfs_file_t' variable. (opened with lv_flashfs_open)
 * @param buf pointer to a memory block which content will be written
 * @param btw the number Bytes To Write
 * @param bw The real number of written bytes (Byte Written)
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv__fs_res_t enum
 */
static lv_fs_res_t lv_flashfs_write(lv_fs_drv_t *drv, void *file_p, const void *buf, uint32_t btw, uint32_t *bw)
{
    uint32_t fd = *(uint32_t *)file_p;
    int32_t ret = 0;

    *bw = 0;
    if (fd == 0) {
        return LV_FS_RES_INV_PARAM;
    }

    uint32_t free_space = FDI_GetFreeSpaceSize();
    if(btw >= free_space) {
        printf("%s: disk full, free_space=%d, btw=%d\n", __FUNCTION__, free_space, btw);
        return LV_FS_RES_FULL;
    }

    ret = FDI_fwrite(buf, 1, btw,  fd);
    *bw = ret;
    if (ret != btw) {
        *bw = 0;
        printf("%s: FDI_fwrite fail, ret=%d, btw=%d\n", __FUNCTION__, ret, btw);

        lv_fs_res_t fs_res = LV_FS_RES_UNKNOWN;
        if(ret <= 0) {
#ifdef USE_WATCH_LITE
            if(0) {
#else
            if(fs_mode_is_tfs()) {
#endif
                uint8_t err = FDI_ferror(fd);
                printf("%s: err=%d\n", __FUNCTION__, err);
                if(ERR_FILE_NO_SPACE == err) {
                    fs_res = LV_FS_RES_FULL;
                }
            } else {
                if(LFS_ERR_NOSPC == ret) {
                    fs_res = LV_FS_RES_FULL;
                }
            }
        }
        return fs_res;
    }

    return LV_FS_RES_OK;
}

/**
 * Set the read write pointer. Also expand the file size if necessary.
 * @param file_p pointer to an 'flashfs_file_t' variable. (opened with lv_flashfs_open )
 * @param pos the new position of read write pointer
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv__fs_res_t enum
 */
static lv_fs_res_t lv_flashfs_seek(lv_fs_drv_t *drv, void *file_p, uint32_t pos)
{
    uint32_t fd = *(uint32_t *)file_p;
    int32_t ret = 0;

    if (fd == 0) {
        return LV_FS_RES_INV_PARAM;
    }

    ret = FDI_fseek(fd, pos, SEEK_SET);
    if (ret < 0)
        return LV_FS_RES_UNKNOWN;

    return LV_FS_RES_OK;
}

/**
 * Give the size of the file in bytes
 * @param file_p file_p pointer to an 'flashfs_file_t' variable. (opened with
 * lv_flashfs_open )
 * @param size_p pointer to store the size
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv__fs_res_t enum
 */
static lv_fs_res_t lv_flashfs_size(lv_fs_drv_t *drv, void *file_p, uint32_t *size_p)
{
    int32_t fd = *(uint32_t *)file_p;

    if (fd == 0) {
        return LV_FS_RES_INV_PARAM;
    }

    *size_p = FDI_GetFileSize(fd);

    return LV_FS_RES_OK;
}

/**
 * Rename flash file (C:)
 * @param oldname
 * @param newname
 * @return LV_FS_RES_OK: no error, the file is read
 *         any error from lv__fs_res_t enum
 */
static lv_fs_res_t lv_flashfs_rename(lv_fs_drv_t * drv, const char * oldname, const char * newname)
{
    int32_t ret = 0;

    ret = FDI_rename(oldname, newname);
    if (ret != 0)
        return LV_FS_RES_UNKNOWN;

    return LV_FS_RES_OK;
}

static lv_fs_res_t lv_flashfs_tell(lv_fs_drv_t *drv, void *file_p, uint32_t *pos_p)
{
    uint32_t fd = *(uint32_t *)file_p;

    if (fd == 0) {
        return LV_FS_RES_INV_PARAM;
    }

    *pos_p = FDI_ftell(fd);

    return LV_FS_RES_OK;
}

static lv_fs_res_t lv_flashfs_free(lv_fs_drv_t * drv, uint32_t * total_p, uint32_t * free_p)
{
    *free_p = FDI_GetFreeSpaceSize();
    *total_p = FDI_GetFreeSpaceSize() + FDI_GetUsedSpaceSize();

    return LV_FS_RES_OK;
}

/**************************************************************************************************************
 *   GLOBAL FUNCTIONS
 **************************************************************************************************************/
/**
 * Create a driver for flashfs and initialize it.
 */
void lv_flashfs_init(void)
{
    lv_fs_drv_t flashfs_drv;

    if (flashfs_inited) {
        printf("flash fs has been inited\n");
        return;
    }

    memset(&flashfs_drv, 0, sizeof(lv_fs_drv_t));

    flashfs_drv.file_size  = sizeof(uint32_t);
    flashfs_drv.letter     = FLASHFS_LETTER;
    flashfs_drv.ready_cb      = lv_flashfs_ready;

    flashfs_drv.open_cb   = lv_flashfs_open;
    flashfs_drv.close_cb  = lv_flashfs_close;
    flashfs_drv.remove_cb = lv_flashfs_remove;
    flashfs_drv.read_cb   = lv_flashfs_read;
    flashfs_drv.write_cb  = lv_flashfs_write;
    flashfs_drv.seek_cb   = lv_flashfs_seek;
    flashfs_drv.size_cb   = lv_flashfs_size;
    flashfs_drv.rename_cb   = lv_flashfs_rename;
    flashfs_drv.tell_cb   = lv_flashfs_tell;
    flashfs_drv.free_space_cb   = lv_flashfs_free;

    lv_fs_drv_register(&flashfs_drv);

    flashfs_inited = 1;
}

#endif /* LV_USE_FILESYSTEM && USE_LV_FLASH_FS */
